import xf from './xfetch';

class GoogleDrive {
    constructor(auth) {
        this.auth = auth;
        this.expires = 0;
        this._getIdCache = new Map();
    }
    async initializeClient() {
        // any method that do api call must call this beforehand
        if (Date.now() < this.expires) return;
        const resp = await xf
            .post('https://www.googleapis.com/oauth2/v4/token', {
                urlencoded: {
                    client_id: this.auth.client_id,
                    client_secret: this.auth.client_secret,
                    refresh_token: this.auth.refresh_token,
                    grant_type: 'refresh_token'
                }
            })
            .json();
        this.client = xf.extend({
            baseURI: 'https://www.googleapis.com/drive/v3/',
            headers: {
                Authorization: `Bearer ${resp.access_token}`
            }
        });
        this.expires = Date.now() + 3500 * 1000; // normally, it should expiers after 3600 seconds
    }
    async listDrive() {
        await this.initializeClient();
        return this.client.get('drives').json();
    }
    async download(id, range = '') {
        await this.initializeClient();
        return this.client.get(`files/${id}`, {
            qs: {
                includeItemsFromAllDrives: true,
                supportsAllDrives: true,
                alt: 'media'
            },
            headers: {
                Range: range
            }
        });
    }
    async downloadByPath(path, rootId = 'root', range = '') {
        const id = await this.getId(path, rootId);
        if (!id) return null;
        return this.download(id, range);
    }
    async getMeta(id) {
        await this.initializeClient();
        return this.client
            .get(`files/${id}`, {
                qs: {
                    includeItemsFromAllDrives: true,
                    supportsAllDrives: true,
                    fields: '*'
                }
            })
            .json();
    }
    async getMetaByPath(path, rootId = 'root') {
        const id = await this.getId(path, rootId);
        if (!id) return null;
        return this.getMeta(id);
    }
    async listFolder(id) {
        await this.initializeClient();
        const getList = pageToken => {
            const qs = {
                includeItemsFromAllDrives: true,
                supportsAllDrives: true,
                q: `'${id}' in parents and trashed = false`,
                orderBy: 'folder,name,modifiedTime desc',
                fields: 'files(id,name,mimeType,size,modifiedTime),nextPageToken',
                pageSize: 1000
            };
            if (pageToken) {
                qs.pageToken = pageToken;
            }
            return this.client
                .get('files', {
                    qs
                })
                .json();
        };
        const files = [];
        let pageToken;
        do {
            const resp = await getList(pageToken);
            files.push(...resp.files);
            pageToken = resp.nextPageToken;
        } while (pageToken);
        return { files };
    }
    async listFolderByPath(path, rootId = 'root') {
        const id = await this.getId(path, rootId);
        if (!id) return null;
        return this.listFolder(id);
    }
    async getId(path, rootId = 'root') {
        const toks = path
            .split('/')
            .map(t => t.replace('$_', '/'))
            .filter(Boolean);
        let id = rootId;
        for (const tok of toks) {
            id = await this._getId(id, tok);
        }
        return id;
    }
    async _getId(parentId, childName) {
        if (this._getIdCache.has(parentId + childName)) {
            return this._getIdCache.get(parentId + childName);
        }
        await this.initializeClient();
        childName = childName.replace(/\'/g, `\\'`); // escape single quote
        const resp = await this.client
            .get('files', {
                qs: {
                    includeItemsFromAllDrives: true,
                    supportsAllDrives: true,
                    q: `'${parentId}' in parents and name = '${childName}'  and trashed = false`,
                    fields: 'files(id)'
                }
            })
            .json()
            .catch(e => ({ files: [] })); // if error, make it empty
        if (resp.files.length === 0) {
            return null;
        }
        this._getIdCache.has(parentId + childName);
        return resp.files[0].id; // when there are more than 1 items, simply return the first one
    }
    async upload(parentId, name, file) {
        await this.initializeClient();
        const createResp = await this.client.post('https://www.googleapis.com/upload/drive/v3/files', {
            qs: {
                uploadType: 'resumable',
                supportsAllDrives: true
            },
            json: {
                name,
                parents: [parentId]
            }
        });
        const putUrl = createResp.headers.get('Location');
        return this.client
            .put(putUrl, {
                body: file
            })
            .json();
    }
    async uploadByPath(path, name, file, rootId = 'root') {
        const id = await this.getId(path, rootId);
        if (!id) return null;
        return this.upload(id, name, file);
    }
    async delete(fileId) {
        return this.client.delete(`files/${fileId}`);
    }
    async deleteByPath(path, rootId = 'root') {
        const id = await this.getId(path, rootId);
        if (!id) return null;
        return this.delete(id);
    }
    async copy(fileId, parentId, fileName = null) {
        this.initializeClient();
        const body = {};
        if (parentId) {
            body.parents = [parentId];
        }
        if (fileName) {
            body.name = fileName;
        }

        return this.client
            .post(`files/${fileId}/copy`, {
                json: body
            })
            .json();
    }
    async existsInParent(name, parentId) {
        await this.initializeClient();
        const getList = () => {
            const qs = {
                includeItemsFromAllDrives: true,
                supportsAllDrives: true,
                q: `name = '${name}' and '${parentId}' in parents and trashed = false`,
                orderBy: 'folder,name,modifiedTime desc',
                fields: 'files(id,name,mimeType,size,modifiedTime),nextPageToken',
                pageSize: 1
            };
            return this.client
                .get('files', {
                    qs
                })
                .json();
        };

        const files = [];
        const resp = await getList();
        files.push(...resp.files);
        const multipleFileExists = Boolean(resp.nextPageToken);

        return {
            file: files ? files[0] : null,
            exists: Boolean(files.length),
            multiple: multipleFileExists
        };
    }
}
export default GoogleDrive;
